using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using RoketSolutions.SP2010.Infrostructure.Common.Extensions;

namespace RoketSolutions.SP2010.Infrostructure.Security
{
    public class SecurityManager
    {
        #region / Public Methods /

        public void Grant(SecurityState state)
        {
            if (state == null)
                throw new ArgumentNullException("state", "To grant object permissions provide new security state");

            if (state.Permissions.Any())
            {
                Do(state.Securable, (site, web) =>
                                        {
                                            if (!state.Securable.HasUniqueRoleAssignments)
                                                state.Securable.BreakRoleInheritance(state.IsInherited);

                                            var principal = ResolvePrincipal(web, state.Principal);
                                            var assignment = new SPRoleAssignment(principal);

                                            foreach (var permission in state.Permissions)
                                                assignment.RoleDefinitionBindings.Add(ResolveRoleDefinition(web, permission));

                                            state.Securable.RoleAssignments.Add(assignment);
                                        });
            }
        }

        public void Revoke(SecurityState state)
        {
            if (state == null)
                throw new ArgumentNullException("state", "To revoke object security state provide state");

            if (state.Permissions.Count > 0)
            {
                Do(state.Securable, (site, web) =>
                                        {
                                            if (state.IsInherited)
                                                state.Securable.ResetRoleInheritance();
                                            else
                                            {
                                                var principal = ResolvePrincipal(web, state.Principal);
                                                var assignment = GetAssignment(state.Securable, principal);

                                                if (assignment != null)
                                                {
                                                    foreach (var permission in state.Permissions)
                                                        assignment.RoleDefinitionBindings.Remove(ResolveRoleDefinition(web, permission));

                                                    assignment.Update();
                                                }
                                            }
                                        });
            }
        }

        public void Revoke(ISecurableObject obj, Principal principal)
        {
            Do(obj, (site, web) =>
                        {
                            if (principal != null)
                            {
                                var p = ResolvePrincipal(web, principal);
                                if (p != null)
                                {
                                    var assignment = GetAssignment(obj, p);
                                    if (assignment != null)
                                        obj.RoleAssignments.Remove(p);
                                }
                            }
                        });
        }

        public SecurityState GetState(ISecurableObject securable, Principal principal)
        {
            if (securable == null)
                throw new ArgumentNullException("securable", "To get security object's state object should be provided");
            if (principal == null)
                throw new ArgumentNullException("principal", "To get security object's state principal should be provided");

            var permissions = new PermissionSet();
            Do(securable, (site, web) =>
                              {
                                  var webPrincipal = ResolvePrincipal(web, principal);
                                  var assignment = GetAssignment(securable, webPrincipal);
                                  if (assignment != null)
                                  {
                                      permissions.AddRange(
                                          from SPRoleDefinition binding in assignment.RoleDefinitionBindings
                                          select new Permission(binding.Name));
                                  }
                              });

            return new SecurityState(principal, permissions, securable, !securable.HasUniqueRoleAssignments);
        }

        public bool IsUserInGroup(SPUser user, string groupName)
        {
            var result = false;

            AdminContext.Do(user.ParentWeb, 
                (site,web)=>
                    {
                        result = IsUserInGroup(web, groupName, user.LoginName);
                    });

            return result;
        }

        public IList<SPGroup> GetUserGroups(SPUser user)
        {
            var result = new List<SPGroup>();

            AdminContext.Do(user.ParentWeb,
                (site, web) => result.AddRange(web.SiteGroups.Cast<SPGroup>().Where(group => IsUserInGroup(web, group.Name, user.LoginName))));

            return result;
        }

        public IList<SPGroup> GetUserGroups(SPUser user, IEnumerable<string> groupsToSearch)
        {
            var result = new List<SPGroup>();

            AdminContext.Do(user.ParentWeb, 
                (site, web)=>
                    {
                        foreach (var groupName in groupsToSearch)
                        {
                            SPGroup group;
                            if (TryGetGroup(web, groupName, out group) && IsUserInGroup(web, groupName, user.LoginName))
                            {
                                result.Add(group);
                            }
                        }
                    });

            return result;
        }
        
        #endregion

        #region / Helper Methods /

        protected bool IsUserInGroup(SPWeb web, string groupName, string loginName)
        {
            try
            {
                return web.SiteGroups[groupName].Users[loginName] != null;
            }
            catch (Exception)
            {
                return false;
            }
        }

        protected bool TryGetGroup(SPWeb web, string groupName, out SPGroup group)
        {
            group = null;
            try
            {
                group = web.SiteGroups[groupName];
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        protected SPRoleAssignment GetAssignment(ISecurableObject securable, SPPrincipal principal)
        {
            SPRoleAssignment assignment = null;

            try
            {
                assignment = securable.RoleAssignments.GetAssignmentByPrincipal(principal);
            }
            catch (ArgumentException)
            {
            }

            return assignment;
        }

        protected SPPrincipal ResolvePrincipal(SPWeb web, Principal principal)
        {
            if (principal != null && !principal.Login.IsNullOrEmpty())
            {
                try
                {
                    var spPrincipal = SPUtility.ResolvePrincipal(web, principal.Login,
                                                                 SPPrincipalType.User | SPPrincipalType.SecurityGroup |
                                                                 SPPrincipalType.SharePointGroup,
                                                                 SPPrincipalSource.All, null, false);
                    if (spPrincipal != null)
                    {
                        if (spPrincipal.PrincipalType == SPPrincipalType.SecurityGroup ||
                            spPrincipal.PrincipalType == SPPrincipalType.SharePointGroup)
                            return web.SiteGroups[spPrincipal.LoginName];
                        return web.SiteUsers[spPrincipal.LoginName];
                    }
                }
                catch (SPException)
                {
                }
            }
            return null;
        }

        protected SPRoleDefinition ResolveRoleDefinition(SPWeb web, Permission permission)
        {
            return web.RoleDefinitions[permission.Name];
        }

        protected void Do(ISecurableObject securable, Action<SPSite, SPWeb> operation)
        {
            if (operation != null)
            {
                AdminContext.Do(securable,
                                (site, web) =>
                                    {
                                        var allowUnsafeUpdates = web.AllowUnsafeUpdates;
                                        try
                                        {
                                            web.AllowUnsafeUpdates = true;
                                            operation(site, web);
                                        }
                                        finally
                                        {
                                            web.AllowUnsafeUpdates = allowUnsafeUpdates;
                                        }
                                    });
            }
        }

        #endregion

    }
}